package com.liuche.book.auth.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.liuche.book.auth.constant.RedisKeyConstants;
import com.liuche.book.auth.constant.RoleConstants;
import com.liuche.book.auth.dao.RoleDao;
import com.liuche.book.auth.dao.UserDao;
import com.liuche.book.auth.dao.mapper.UserMapper;
import com.liuche.book.auth.dao.mapper.UserRoleRelMapper;
import com.liuche.book.auth.enums.LoginTypeEnum;
import com.liuche.book.auth.model.entity.Role;
import com.liuche.book.auth.model.entity.User;
import com.liuche.book.auth.model.entity.UserRoleRel;
import com.liuche.book.auth.model.vo.user.req.UpdatePasswordReqVO;
import com.liuche.book.auth.model.vo.user.req.UserLoginReqVO;
import com.liuche.book.auth.service.UserService;
import com.liuche.framework.biz.context.holder.UserContextHolder;
import com.liuche.framework.common.constant.CommonRedisKey;
import com.liuche.framework.common.enums.ResponseCodeEnum;
import com.liuche.framework.common.enums.UserErrorEnum;
import com.liuche.framework.common.exception.BizException;
import com.liuche.framework.common.response.Response;
import com.liuche.framework.common.service.AopProxy;
import com.liuche.framework.common.util.JsonUtil;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;


@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService, AopProxy<UserServiceImpl> {
    private static final String DEFAULT_AVATAR_IMG = "https://img.liuche17.cn/weblog/c1e64147da8144c98babf7662e1d7e9c.jpg";
    private static final String DEFAULT_INTRODUCTION = "人生需要动态规划，学习需要贪心算法";

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private UserRoleRelMapper userRoleRelMapper;

    @Resource
    private RoleDao roleDao;
    @Resource
    private UserDao userDao;
    @Resource
    private PasswordEncoder passwordEncoder;

    @Override
    public Response<String> login(UserLoginReqVO vo) {
        String phone = vo.getPhone();
        // 得到用户登录类型
        LoginTypeEnum loginTypeEnum = Optional.ofNullable(LoginTypeEnum.valueOf(vo.getType()))
                                              .orElseThrow(()-> new BizException(ResponseCodeEnum.PARAM_NOT_VALID));
        Long userId = switch (loginTypeEnum) {
            case VERIFICATION_CODE -> doLoginByCode(vo);
            case PASSWORD -> doLoginByPWD(vo);
        };
        // 删除验证码缓存信息
        stringRedisTemplate.delete(RedisKeyConstants.buildVerificationCodeKey(phone));
        // 返回 token 令牌
        StpUtil.login(userId);
        String token = StpUtil.getTokenValue();

        return Response.success(token);
    }

    private Long doLoginByPWD(UserLoginReqVO vo) {
        String phone = vo.getPhone();
        User user = this.baseMapper.queryUserInfoByPhone(phone);
        if (ObjectUtils.isEmpty(user)) {
            throw new BizException(UserErrorEnum.USER_VERIFY_ERROR);
        }
        boolean flag = passwordEncoder.matches(vo.getPassword(), user.getPassword());
        if (!flag) {
            throw new BizException(UserErrorEnum.USER_PWD_ERROR);
        }
        return user.getId();
    }

    private Long doLoginByCode(UserLoginReqVO vo) {
        String phone = vo.getPhone();
        String code = vo.getCode();
        // 校验验证码
        String verifyCode = Optional.ofNullable(stringRedisTemplate.opsForValue()
                        .get(RedisKeyConstants.buildVerificationCodeKey(phone)))
                .orElse("");
        if (StringUtils.isBlank(verifyCode) || !code.equals(verifyCode)) {
            throw new BizException(UserErrorEnum.USER_CODE_VERIFY_ERROR);
        }

        // 判断是否注册过
        User user = this.baseMapper.queryUserInfoByPhone(phone);

        // 如若查询到为 null 则说明是注册
        if (ObjectUtils.isEmpty(user)) {
            user = self().registryUser(phone);
        }
        return user.getId();
    }

    @Override
    public Response<?> logout() {
        StpUtil.logout();
        return Response.success();
    }

    @Override
    public Response<?> updatePwd(UpdatePasswordReqVO vo) {
        String newPassword = vo.getNewPassword();
        // 加密
        String encodePWD = passwordEncoder.encode(newPassword);
        User build = User.builder()
                .id(UserContextHolder.getUserId())
                .password(encodePWD)
                .build();
        userDao.updateById(build);

        return Response.success();
    }

    /**
     * 系统自动注册用户
     * @param phone
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public User registryUser(String phone) {
        // 构建用户基本信息
        String bookId = generateBookId();
        User user = User.builder()
                .avatar(DEFAULT_AVATAR_IMG)
                .nickname("red_book_" + bookId)
                .phone(phone)
                .bookId(bookId)
                .introduction(DEFAULT_INTRODUCTION).build();

        // 保存用户信息
        this.baseMapper.insert(user);

        // 构建用户权限信息
        UserRoleRel userRoleRel = UserRoleRel.builder()
                .roleId(RoleConstants.COMMON_USER_ROLE_ID)
                .userId(user.getId())
                .build();

        // 保存角色、用户信息
        Role role = roleDao.queryRoleInfo(RoleConstants.COMMON_USER_ROLE_ID);
        List<String> roles = Lists.newArrayList(role.getRoleKey());
        stringRedisTemplate.opsForValue().set(CommonRedisKey.buildUserRoleKey(user.getId()), JsonUtil.toJsonString(roles));
        userRoleRelMapper.insert(userRoleRel);

        return user;
    }

    /**
     * 生成小红书唯一认证 id
     * @return
     */
    private String generateBookId() {
        Long bookId = stringRedisTemplate.opsForValue().increment(RedisKeyConstants.buildBookDefaultIdKey());
        return bookId.toString();
    }
}
